package com.xul.srb.core.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xul.common.exception.BusinessException;
import com.xul.srb.core.enums.LendStatusEnum;
import com.xul.srb.core.enums.ReturnMethodEnum;
import com.xul.srb.core.enums.TransTypeEnum;
import com.xul.srb.core.hfb.HfbConst;
import com.xul.srb.core.hfb.RequestHelper;
import com.xul.srb.core.mapper.BorrowerMapper;
import com.xul.srb.core.mapper.LendMapper;
import com.xul.srb.core.mapper.UserAccountMapper;
import com.xul.srb.core.mapper.UserInfoMapper;
import com.xul.srb.core.pojo.bo.TransFlowBO;
import com.xul.srb.core.pojo.entity.*;
import com.xul.srb.core.pojo.vo.BorrowInfoApprovalVO;
import com.xul.srb.core.pojo.vo.BorrowerDetailVO;
import com.xul.srb.core.service.*;
import com.xul.srb.core.util.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>
 * 标的准备表 服务实现类
 * </p>
 *
 * @author xul
 * @since 2021-11-06
 */
@Service
@Slf4j
public class LendServiceImpl extends ServiceImpl<LendMapper, Lend> implements LendService {

    @Resource
    private DictService dictService;

    @Resource
    private BorrowerMapper borrowerMapper;

    @Resource
    private BorrowerService borrowerService;

    @Resource
    private UserAccountMapper userAccountMapper;

    @Resource
    private UserInfoMapper userInfoMapper;

    @Resource
    private TransFlowService transFlowService;

    @Resource
    private LendItemService lendItemService;

    @Resource
    private LendItemReturnService lendItemReturnService;

    @Resource
    private LendReturnService lendReturnService;

    /**
     * 审核通过会产生新的标的 存入相关信息到标的表
     *
     * @param borrowInfo
     * @param borrowInfoApprovalVO
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createLend(BorrowInfo borrowInfo, BorrowInfoApprovalVO borrowInfoApprovalVO) {
        Lend lend = new Lend();
        // 用户id
        lend.setUserId(borrowInfo.getUserId());
        // 借款信息id
        lend.setBorrowInfoId(borrowInfo.getId());
        // 设置标的编号
        lend.setLendNo(LendNoUtils.getLendNo());
        // 设置标题
        lend.setTitle(borrowInfoApprovalVO.getTitle());
        // 设置借款金额
        lend.setAmount(borrowInfo.getAmount());
        // 设置期数
        lend.setPeriod(borrowInfo.getPeriod());
        // 设置年化利率
        lend.setLendYearRate(borrowInfoApprovalVO.getLendYearRate().divide(new BigDecimal(100)));
        // 设置平台服务费率
        lend.setServiceRate(borrowInfoApprovalVO.getServiceRate().divide(new BigDecimal(100)));
        // 设置还款方式
        lend.setReturnMethod(borrowInfo.getReturnMethod());
        // 设置最低投资金额 默认一个初始值100
        lend.setLowestAmount(new BigDecimal(100));
        // 设置已投金额
        lend.setInvestAmount(new BigDecimal(0));
        // 设置投资人数
        lend.setInvestNum(0);
        // 设置发布日期
        lend.setPublishDate(LocalDateTime.now());
        // 设置起薪日期
        DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        LocalDate lendStartDate = LocalDate.parse(borrowInfoApprovalVO.getLendStartDate(), pattern);
        lend.setLendStartDate(lendStartDate);
        // 设置结束日期
        LocalDate lendEndDate = lendStartDate.plusMonths(borrowInfo.getPeriod());
        lend.setLendEndDate(lendEndDate);
        // 设置说明
        lend.setLendInfo(borrowInfoApprovalVO.getLendInfo());
        // 设置平台预期收益 平台服务费率(年化)/12 * 标的金额 * 期数
        BigDecimal monthRate = lend.getServiceRate().divide(new BigDecimal(12), 8, BigDecimal.ROUND_DOWN);
        BigDecimal expectAmount = lend.getAmount().multiply(monthRate).multiply(new BigDecimal(lend.getPeriod()));
        lend.setExpectAmount(expectAmount);
        // 设置实际收益
        lend.setRealAmount(new BigDecimal(0));
        // 设置状态
        lend.setStatus(LendStatusEnum.INVEST_RUN.getStatus());
        // 设置审核时间
        lend.setCheckTime(LocalDateTime.now());
        // 设置审核人 (应该在后台系统获取 后台系统没有配置 默认设置1L)
        lend.setCheckAdminId(1L);
        // 保存标的信息
        baseMapper.insert(lend);
    }

    /**
     * 获取标的信息
     *
     * @param id
     * @return
     */
    @Override
    public Map<String, Object> show(Long id) {

        // 组装标的信息
        Lend lend = baseMapper.selectById(id);
        String returnMethod = dictService.getNameByDictCodeAndValue(lend.getReturnMethod(), "returnMethod");
        String status = LendStatusEnum.getMsgByStatus(lend.getStatus());
        lend.getParam().put("returnMethod", returnMethod);
        lend.getParam().put("status", status);

        // 组装借款人信息
        QueryWrapper<Borrower> wrapper = new QueryWrapper<>();
        wrapper.eq("user_id", lend.getUserId());
        Borrower borrower = borrowerMapper.selectOne(wrapper);
        BorrowerDetailVO borrowerDetailVOById = borrowerService.getBorrowerDetailVOById(borrower.getId());
        HashMap<String, Object> result = new HashMap<>();
        result.put("lend", lend);
        result.put("borrower", borrowerDetailVOById);
        return result;
    }

    /**
     * 计算投资收益
     *
     * @param invest
     * @param yearRate
     * @param totalmonth
     * @param returnMethod
     * @return
     */
    @Override
    public BigDecimal getInterestCount(BigDecimal invest, BigDecimal yearRate, Integer totalmonth, Integer returnMethod) {
        BigDecimal interestCount;
        if (returnMethod.intValue() == ReturnMethodEnum.ONE.getMethod()) {
            interestCount = Amount1Helper.getInterestCount(invest, yearRate, totalmonth);
        } else if (returnMethod.intValue() == ReturnMethodEnum.TWO.getMethod()) {
            interestCount = Amount2Helper.getInterestCount(invest, yearRate, totalmonth);
        } else if (returnMethod.intValue() == ReturnMethodEnum.THREE.getMethod()) {
            interestCount = Amount3Helper.getInterestCount(invest, yearRate, totalmonth);
        } else {
            interestCount = Amount4Helper.getInterestCount(invest, yearRate, totalmonth);
        }
        return interestCount;
    }

    /**
     * 放款
     *
     * @param id
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void makeLoan(Long id) {
        // 获取到标的信息
        Lend lend = baseMapper.selectById(id);

        // 组装放款调用的参数
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("agentId", HfbConst.AGENT_ID);
        // 标的编号
        paramMap.put("agentProjectCode", lend.getLendNo());
        // 放款编号
        String agentBillNo = LendNoUtils.getLoanNo();
        paramMap.put("agentBillNo", agentBillNo);

        // 平台收益 在放款的时候就扣除 借款的借的实际金额 = 借款金额 - 平台的收益
        // 服务费的月利化
        BigDecimal monthServiceRate = lend.getServiceRate().divide(new BigDecimal(12), 8, BigDecimal.ROUND_DOWN);
        // 平台的收益 = 月利化 * 标的的期数 * 已投的金额
        BigDecimal realServiceAmount = lend.getAmount().multiply(new BigDecimal(lend.getPeriod())).multiply(monthServiceRate);
        // 平台手续费 商户手续费(平台实际收益)
        paramMap.put("mchFee", realServiceAmount);
        // 时间搓
        paramMap.put("timestamp", RequestHelper.getTimestamp());
        // 签名的加密
        String sign = RequestHelper.getSign(paramMap);
        paramMap.put("sign", sign);

        // 放款的参数
        log.info("放款的参数:" + JSONObject.toJSONString(paramMap));
        // 发起同步远程调用
        JSONObject result = RequestHelper.sendRequest(paramMap, HfbConst.MAKE_LOAD_URL);
        log.info("放款的结果:" + JSONObject.toJSONString(result));

        // 放款失败
        if (!"0000".equals(result.getString("resultCode"))) {
            throw new BusinessException(result.getString("resultMsg"));
        }

        // 更新标的信息
        lend.setRealAmount(realServiceAmount);
        lend.setStatus(LendStatusEnum.PAY_RUN.getStatus());
        lend.setPaymentTime(LocalDateTime.now());
        baseMapper.updateById(lend);

        // 获取到借款人的信息
        Long userId = lend.getUserId();
        // 获取到向借款人账号转入的金额
        BigDecimal voteAmt = new BigDecimal(result.getString("voteAmt"));
        // 添加相应的金额到数据库
        userAccountMapper.updateAccountLend(userId, voteAmt, new BigDecimal(0));

        // 新增交易流水号
        UserInfo userInfo = userInfoMapper.selectById(userId);
        String bindCode = userInfo.getBindCode();
        TransFlowBO transFlowBO = new TransFlowBO(
                agentBillNo,
                bindCode,
                voteAmt,
                TransTypeEnum.BORROW_BACK,
                // 项目编号
                "借款放款到账，编号：" + lend.getLendNo());
        transFlowService.insertTransFlow(transFlowBO);

        // 获取投资人的列表的信息
        List<LendItem> lendItemList = lendItemService.selectByLendId(id, 1);

        lendItemList.forEach(lendItem -> {
            // 获取当前标的 lend_id 下的投资人的id
            Long investUserId = lendItem.getInvestUserId();
            // 获取到投资人的用户信息
            UserInfo investUserInfo = userInfoMapper.selectById(investUserId);
            String investBindCode = investUserInfo.getBindCode();
            // 把投资人的冻结金额转出
            BigDecimal investAmount = lendItem.getInvestAmount();
            userAccountMapper.updateAccountLend(investUserId, new BigDecimal(0), investAmount.negate());

            //新增投资人交易流水
            TransFlowBO investTransFlowBO = new TransFlowBO(
                    LendNoUtils.getTransNo(),
                    investBindCode,
                    investAmount,
                    TransTypeEnum.INVEST_UNLOCK,
                    "冻结资金转出，出借放款，编号：" + lend.getLendNo());//项目编号
            transFlowService.insertTransFlow(investTransFlowBO);
        });

        // 放款成功后生成 借款人的还款计划 和 投资人的回款计划
        repaymentPlan(lend);
    }

    /**
     * 还款计划
     *
     * @param lend
     */
    private void repaymentPlan(Lend lend) {
        // 初始化还款计划列表
        List<LendReturn> lendReturnList = new ArrayList<>();

        // 按照还款时间生产还款计划
        int len = lend.getPeriod();

        for (int i = 1; i <= len; i++) {
            // 组装还款计划对象
            LendReturn lendReturn = new LendReturn();
            // 还款批次号
            lendReturn.setReturnNo(LendNoUtils.getReturnNo());
            // 标的id
            lendReturn.setLendId(lend.getId());
            // 借款信息id
            lendReturn.setBorrowInfoId(lend.getBorrowInfoId());
            lendReturn.setUserId(lend.getUserId());
            // 借款用户id
            lendReturn.setAmount(lend.getAmount());
            // 已投金额
            lendReturn.setBaseAmount(lend.getInvestAmount());
            // 年化利率
            lendReturn.setLendYearRate(lend.getLendYearRate());
            // 当前期数
            lendReturn.setCurrentPeriod(i);
            // 还款方式
            lendReturn.setReturnMethod(lend.getReturnMethod());

            //说明：还款计划中的这三项 = 回款计划中对应的这三项和：因此需要先生成对应的回款计划
            //			lendReturn.setPrincipal();
            //			lendReturn.setInterest();
            //			lendReturn.setTotal();


            // 服务费的月利化
            BigDecimal monthServiceRate = lend.getServiceRate().divide(new BigDecimal(12), 8, BigDecimal.ROUND_DOWN);
            // 平台的收益 = 月利化 数 * 已投的金额 * 当前一期
            BigDecimal fee = lend.getAmount().multiply(monthServiceRate);
            // 每一期的手续费
            lendReturn.setFee(fee);
            // 还款时指定的还款日期 第二个月开始还款
            lendReturn.setReturnDate(lend.getLendStartDate().plusMonths(i));
            lendReturn.setOverdue(false);
            if (i == len) {
                lendReturn.setLast(true);
            } else {
                lendReturn.setLast(false);
            }
            // 状态（0-未归还 1-已归还）
            lendReturn.setStatus(0);
            lendReturnList.add(lendReturn);
        }

        lendReturnService.saveBatch(lendReturnList);

        // 初始化回款计划表
        List<LendItemReturn> lendItemReturnAllList = new ArrayList<>();

        // 获取到所有投资成功的投资记录
        List<LendItem> lendItemList = lendItemService.selectByLendId(lend.getId(), 1);
        lendItemList.forEach(lendItem -> {
            // 创建回款计划表
            List<LendItemReturn> lendItemReturnList = this.returnInvest(lendItem, lendReturnList, lend);
            lendItemReturnAllList.addAll(lendItemReturnList);
        });

        // 更新相关还款数据
        lendReturnList.forEach(lendReturn -> {

            BigDecimal sumPrincipal = lendItemReturnAllList.stream()
                    // 过滤条件：当回款计划中的还款计划id == 当前还款计划id的时候
                    .filter(item -> item.getLendReturnId().intValue() == lendReturn.getId().intValue())
                    // 将所有回款计划中计算的每月应收本金相加
                    .map(LendItemReturn::getPrincipal)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);


            BigDecimal sumInterest = lendItemReturnAllList.stream()
                    // 过滤条件：当回款计划中的还款计划id == 当前还款计划id的时候
                    .filter(item -> item.getLendReturnId().intValue() == lendReturn.getId().intValue())
                    // 将所有回款计划中计算的每月应收本金相加
                    .map(LendItemReturn::getInterest)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            BigDecimal sumTotal = lendItemReturnAllList.stream()
                    // 过滤条件：当回款计划中的还款计划id == 当前还款计划id的时候
                    .filter(item -> item.getLendReturnId().intValue() == lendReturn.getId().intValue())
                    // 将所有回款计划中计算的每月应收本金相加
                    .map(LendItemReturn::getTotal)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            lendReturn.setPrincipal(sumPrincipal);
            lendReturn.setInterest(sumInterest);
            lendReturn.setTotal(sumTotal);
        });
        lendReturnService.updateBatchById(lendReturnList);

    }

    /**
     * 回款计划
     *
     * @param lendItem       每一笔投资人的投标信息
     * @param lendReturnList
     * @param lend
     * @return
     */
    private List<LendItemReturn> returnInvest(LendItem lendItem, List<LendReturn> lendReturnList, Lend lend) {
        // 每一笔投资人投资金额
        BigDecimal amount = lendItem.getInvestAmount();
        // 年化利率
        BigDecimal yearRate = lendItem.getLendYearRate();
        //投资期数
        Integer totalMonth = lend.getPeriod();

        // 初始化还款期数 --> 利息
        Map<Integer, BigDecimal> mapInterest = null;
        // 初始化还款期数 --> 本金
        Map<Integer, BigDecimal> mapPrincipal = null;

        // 根据还款的方式计算本金和利息
        if (lend.getReturnMethod().intValue() == ReturnMethodEnum.ONE.getMethod()) {
            //利息
            mapInterest = Amount1Helper.getPerMonthInterest(amount, yearRate, totalMonth);
            //本金
            mapPrincipal = Amount1Helper.getPerMonthPrincipal(amount, yearRate, totalMonth);
        } else if (lend.getReturnMethod().intValue() == ReturnMethodEnum.TWO.getMethod()) {
            mapInterest = Amount2Helper.getPerMonthInterest(amount, yearRate, totalMonth);
            mapPrincipal = Amount2Helper.getPerMonthPrincipal(amount, yearRate, totalMonth);
        } else if (lend.getReturnMethod().intValue() == ReturnMethodEnum.THREE.getMethod()) {
            mapInterest = Amount3Helper.getPerMonthInterest(amount, yearRate, totalMonth);
            mapPrincipal = Amount3Helper.getPerMonthPrincipal(amount, yearRate, totalMonth);
        } else {
            mapInterest = Amount4Helper.getPerMonthInterest(amount, yearRate, totalMonth);
            mapPrincipal = Amount4Helper.getPerMonthPrincipal(amount, yearRate, totalMonth);
        }

        // 获取lendReturnList中还款期数与还款计划id对应map
        Map<Integer, Long> lendReturnMap = lendReturnList.stream().collect(
                Collectors.toMap(LendReturn::getCurrentPeriod, LendReturn::getId)
        );

        // 初始化回款计划列表
        List<LendItemReturn> lendItemReturnList = new ArrayList<>();

        for (Map.Entry<Integer, BigDecimal> entry : mapInterest.entrySet()) {
            Integer currentPeriod = entry.getKey();

            // 根据还款期数获取到还款id
            Long lendReturnId = lendReturnMap.get(currentPeriod);

            LendItemReturn lendItemReturn = new LendItemReturn();
            // 标的还款id
            lendItemReturn.setLendReturnId(lendReturnId);
            // 标的项id
            lendItemReturn.setLendItemId(lendItem.getId());
            // 出借用户id
            lendItemReturn.setInvestUserId(lendItem.getInvestUserId());
            // 标的id
            lendItemReturn.setLendId(lendItem.getLendId());
            // 出借金额
            lendItemReturn.setInvestAmount(lendItem.getInvestAmount());
            // 年化利率
            lendItemReturn.setLendYearRate(lend.getLendYearRate());
            // 当前的期数
            lendItemReturn.setCurrentPeriod(currentPeriod);
            // 还款方式 1-等额本息 2-等额本金 3-每月还息一次还本 4-一次还本
            lendItemReturn.setReturnMethod(lend.getReturnMethod());

            // 最后一次的本金计算
            if (lendItemReturnList.size() > 0 && currentPeriod.intValue() == lend.getPeriod().intValue()) {
                // 最后一期本金 = 本金 - 前几次之和
                BigDecimal sumPrincipal = lendItemReturnList.stream().map(LendItemReturn::getPrincipal).reduce(BigDecimal.ZERO, BigDecimal::add);
                // 最后一期应还本金 = 用当前投资人的总投资金额 - 除了最后一期前面期数计算出来的所有的应还本金
                BigDecimal lastPrincipal = lendItem.getInvestAmount().subtract(sumPrincipal);
                lendItemReturn.setPrincipal(lastPrincipal);

                // 最后一期利息 = 利息 - 前几次之和
                BigDecimal sumInterest = lendItemReturnList.stream().map(LendItemReturn::getInterest).reduce(BigDecimal.ZERO, BigDecimal::add);
                BigDecimal lastInterest = lendItem.getExpectAmount().subtract(sumInterest);
                lendItemReturn.setInterest(lastInterest);

            } else {
                // 本金
                lendItemReturn.setPrincipal(mapPrincipal.get(currentPeriod));
                // 利息
                lendItemReturn.setInterest(mapInterest.get(currentPeriod));
            }

            // 每期总金额
            lendItemReturn.setTotal(lendItemReturn.getPrincipal().add(lendItemReturn.getInterest()));
            lendItemReturn.setFee(new BigDecimal("0"));
            lendItemReturn.setReturnDate(lend.getLendStartDate().plusMonths(currentPeriod));
            //是否逾期，默认未逾期
            lendItemReturn.setOverdue(false);
            lendItemReturn.setStatus(0);

            lendItemReturnList.add(lendItemReturn);
        }
        // 批量保存
        lendItemReturnService.saveBatch(lendItemReturnList);

        return lendItemReturnList;
    }
}
